/* * Copyright 2012 Jörg Hoh, Alexander Saar, Markus Haack * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.joerghoh.cq5.healthcheck.impl.providers.jcr; import java.util.Dictionary; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.observation.Event; import javax.jcr.observation.EventIterator; import javax.jcr.observation.EventListener; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.ConfigurationPolicy; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.sling.commons.osgi.PropertiesUtil; import org.apache.sling.commons.scheduler.Scheduler; import org.apache.sling.jcr.api.SlingRepository; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.day.cq.commons.jcr.JcrUtil; import de.joerghoh.cq5.healthcheck.Status; import de.joerghoh.cq5.healthcheck.StatusCode; import de.joerghoh.cq5.healthcheck.StatusProvider; /** * JCR Observation Delay Provider * * @author joerg@joerghoh.de (Jörg Hoh) * * This statusprovider delivers information about the current delay in * the JCR observation mechanism. For this reason it creates nodes and * measures the time the creation of the node and the time, when the * creation event is finally delivered to this provider. This time isn't * really accurate, as it also includes the saving of the session (which * shouldn't be a really big problem, since the node is really small), * but in any way it can give you indications about any delays causing * problems with the processing of both JCR observation and Sling * events. * * A clear implementation should be placed into the JCR repository, * which should annotate each event with a timestamp and measure the * delay there. */ @Component(immediate = true, metatype = true, description = "JCR Observation Delay checker", label = "JCR Observation Delay Checker", policy = ConfigurationPolicy.REQUIRE) @Service public class JcrObservationDelayProvider implements EventListener, StatusProvider { private Logger log = LoggerFactory.getLogger(this.getClass()); @Property private static String CATEGORY = "provider.category"; private String category; private static final long DEFAULT_DELAY_WARN = 500; @Property(longValue = DEFAULT_DELAY_WARN, label = "threshold for WARN", description = "Threshold to reach WARN level in miliseconds") private static final String DELAY_WARN = "delay.warn"; private long delayWarn; private static final long DEFAULT_DELAY_CRITICAL = 2000; @Property(longValue = DEFAULT_DELAY_CRITICAL, label = "threshold for CRITICAL", description = "Threshold to reach CRITICAL level in miliseconds") private static final String DELAY_CRITICAL = "delay.critical"; private long delayCritical; private static final long DEFAULT_SCHEDULING_INTERVAL = 60; // seconds @Property(longValue = DEFAULT_SCHEDULING_INTERVAL, label = "Scheduling interval", description = "Interval between checks in seconds") private static final String SCHEDULING_INTERVAL = "scheduling.interval"; private long schedulingInterval; private static final String JOBNAME = "de.joerghoh.cq5.healthcheck.impl.providers.jcr.jcrObservationDelayProvider"; private static final String tempDir = "/var/JcrObservationDelay"; private Session listenSession = null; private long lastDelay = 0; @Reference private Scheduler scheduler; @Reference private SlingRepository repo; @Activate protected void activate(ComponentContext ctx) { Session readWriteSession = null; Dictionary<?, ?> props = ctx.getProperties(); delayWarn = PropertiesUtil.toLong(props.get(DELAY_WARN), DEFAULT_DELAY_WARN); delayCritical = PropertiesUtil.toLong(props.get(DELAY_CRITICAL), DEFAULT_DELAY_CRITICAL); schedulingInterval = PropertiesUtil.toLong(props.get(SCHEDULING_INTERVAL), DEFAULT_SCHEDULING_INTERVAL); category = PropertiesUtil.toString(props.get(CATEGORY), null); try { readWriteSession = repo.loginAdministrative(null); if (!readWriteSession.getRootNode().hasNode( tempDir.replaceFirst("/", ""))) { log.info("Created temp directory"); JcrUtil.createPath(tempDir, "nt:unstructured", readWriteSession); readWriteSession.save(); } // Node tmpNode = readWriteSession.getNode(tempDir); // Long now = System.currentTimeMillis(); // NodeIterator childs = tmpNode.getNodes(); } catch (RepositoryException e) { log.error("Cannot create Session for setting up temp directory:", e); } finally { if (readWriteSession != null && readWriteSession.isLive()) { readWriteSession.logout(); } } try { listenSession = repo.loginAdministrative(null); startListen(); } catch (RepositoryException e) { log.error("Cannot create listenSession", e); } try { scheduler.fireJob(new ObservationTrigger(tempDir), null); scheduler.addPeriodicJob(JOBNAME, new ObservationTrigger(tempDir), null, schedulingInterval, false); } catch (Exception e) { log.error("Cannot start scheduler", e); } log.info("JcrObservationDelay checker started"); } @Deactivate protected void deactivate(ComponentContext ctx) { try { stopListen(); } catch (RepositoryException e) { log.error("Cannot stop observation listener", e); } if (listenSession != null && listenSession.isLive()) { listenSession.logout(); } scheduler.removeJob(JOBNAME); log.info("JcrObservationDelay checker stopped"); } private void stopListen() throws UnsupportedRepositoryOperationException, RepositoryException { listenSession.getWorkspace().getObservationManager() .removeEventListener(this); } private void startListen() throws UnsupportedRepositoryOperationException, RepositoryException { listenSession .getWorkspace() .getObservationManager() .addEventListener(this, Event.NODE_ADDED, tempDir, true, null, null, true); } /** * Do the event handling, calculate delay and drop the node afterwards. */ @Override public void onEvent(EventIterator events) { try { while (events.hasNext()) { Event e = events.nextEvent(); if (e.getPath().startsWith(tempDir)) { Node foundNode = listenSession.getNode(e.getPath()); long now = System.currentTimeMillis(); long timestamp = foundNode.getProperty("timestamp") .getLong(); lastDelay = now - timestamp; log.debug("Current observation delay: {} ms", lastDelay); removeNode(foundNode.getPath()); } } } catch (RepositoryException e) { log.error("Cannot handle event", e); } } private void removeNode(String foundNode) throws RepositoryException { Session writeSession = null; try { writeSession = repo.loginAdministrative(null); writeSession.removeItem(foundNode); writeSession.save(); } finally { if (writeSession != null && writeSession.isLive()) { writeSession.logout(); } } } /** * @see de.joerghoh.cq5.healthcheck.StatusProvider#getStatus() */ public Status getStatus() { StatusCode sc = StatusCode.OK; if (lastDelay > delayCritical) { sc = StatusCode.CRITICAL; } else if (lastDelay > delayWarn) { sc = StatusCode.WARN; } String message = "Delay " + lastDelay + " ms"; String provider = "JcrObservationDelay"; Status status = new Status(sc, message, provider); return status; } /** * @see de.joerghoh.cq5.healthcheck.StatusProvider#getCategory() */ public String getCategory() { return category != null ? category : DEFAULT_CATEGORY; } /** * runnable implementation, which create a change to the repository */ private class ObservationTrigger implements Runnable { private String rootPath; private Logger log = LoggerFactory.getLogger(this.getClass()); protected ObservationTrigger(String rootNode) { this.rootPath = rootNode; } public void run() { createNewNode(); } private void createNewNode() { Session writeSession = null; try { writeSession = repo.loginAdministrative(null); long now = System.currentTimeMillis(); String nodeName = "" + now; Node rootNode = writeSession.getNode(rootPath); Node childNode = rootNode.addNode(nodeName, "nt:unstructured"); ; childNode.setProperty("timestamp", now); writeSession.save(); } catch (RepositoryException e) { log.warn("Cannot create timestamp node", e); } finally { if (writeSession != null && writeSession.isLive()) { writeSession.logout(); } } } } }